#!/bin/sh -u
# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Run TPM diagnostics in recovery mode, and attempt to fix problems.  This is
# specific to devices with chromeos firmware.
#
# Most of the diagnostics examine the TPM state and try to fix it.  This may
# require clearing TPM ownership.

tpmc=${USR_BIN:=/usr/bin}/tpmc
crossystem=${USR_BIN}/crossystem
dot_recovery=${DOT_RECOVERY:=/mnt/stateful_partition/.recovery}
awk=/usr/bin/awk
initctl=/sbin/initctl

log() {
  echo "$*"
}

quit() {
  log "ERROR: $*"
  log "exiting"
  exit 1
}

log_tryfix() {
  log "$*: attempting to fix"
}

tpm_clear_and_reenable () {
  $tpmc clear
  $tpmc enable
  $tpmc activate
}

reset_space () {
  local index=$1
  local permissions=$2
  local size=$3
  local bytes="$4"

  if ! $tpmc definespace $index $size $permissions; then
    log "could not redefine space $index"
    return 1
  fi
  # do not quote "$bytes", as we mean to expand it here
  $tpmc write $index $bytes || log "writing to $index failed with code $?"
  log "space $index was recreated successfully"
}


# ------------
# MAIN PROGRAM
# ------------

# Sanity check: are we executing in a recovery image?

if [ -e $dot_recovery ]; then
  quit "This is a developer utility, it should never run on a (production) recovery image"
fi

# Did the firmware keep the TPM unlocked?

if ! $($crossystem mainfw_type?recovery); then
  quit "You must put a test image on a USB stick and boot it in recovery mode to run this"
fi

# tcsd may or may not be running

log "Stopping tcsd..."
if $initctl stop tcsd >/dev/null 2>/dev/null; then
  tcsd_was_running=1
  log "...done"
else
  tcsd_was_running=0
  log "(already stopped)"
fi

# Is the state of the PP enable flags correct?

if ! ($tpmc getpf | grep -q "physicalPresenceLifetimeLock 1" &&
      $tpmc getpf | grep -q "physicalPresenceHWEnable 0" &&
      $tpmc getpf | grep -q "physicalPresenceCMDEnable 1"); then
  log_tryfix "bad state of physical presence enable flags"
  if $tpmc ppfin; then
    log "physical presence enable flags are now correctly set"
  else
    quit "could not set physical presence enable flags"
  fi
fi

# Is physical presence turned on?

if $tpmc getvf | grep -q "physicalPresence 0"; then
  log_tryfix "physical presence is OFF, expected ON"
  # attempt to turn on physical presence
  if $tpmc ppon; then
    log "physical presence is now on"
  else
    quit "could not turn physical presence on"
  fi
fi

# I never learned what this does, but it's probably good just in case...
tpm_clear_and_reenable

# Reset firmware and kernel spaces to default (rollback version 1/1)
reset_space 0x1007 0x8001 0xa "02  00  01 00 01 00  00 00 00  4f" || \
  log "could not fix firmware space"
reset_space 0x1008 0x1 0xd "02  4c 57 52 47  01 00 01 00  00 00 00  55" || \
  log "could not fix kernel space"
# Don't need valid data in backup space, vboot can reset it as long as it exists
reset_space 0x1009 0x1 0x10 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" || \
  log "could not fix backup space"

if [ $tcsd_was_running != 0 ]; then
  echo Restarting tcsd...
  $initctl start tcsd >/dev/null
fi

log "TPM has successfully been reset to factory defaults"
